package org.glandais.profiler;

import java.io.File;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.Date;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.glandais.profiler.beans.Config;
import org.glandais.profiler.beans.ConfigMBean;
import org.glandais.profiler.beans.TraceStack;

import com.thoughtworks.xstream.XStream;

public class Agent {

	public static ConfigMBean configBean;
	public static final XStream xstream = createXStream();

	static {
		try {
			loadConfig();
			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
			ObjectName name = new ObjectName("org.glandais.profiler:type=Config");
			mbs.registerMBean(configBean, name);
		} catch (Exception e) {
			throw new RuntimeException("Failed to init profiler config", e);
		}
	}

	public static void premain(String agentArgument,
			Instrumentation instrumentation) {
		startAgent(agentArgument, instrumentation);
	}

	public static final XStream createXStream() {
		XStream result = new XStream();
		result.omitField(TraceStack.class, "stack");
		result.alias("config", Config.class);
		return result;
	}

	private static final void loadConfig() throws Exception {
		File f = null;

		String systemConfig = System.getProperty("profiler.config");

		if (systemConfig != null) {
			File fsys = new File(systemConfig);
			f = fsys;
			if (!f.exists()) {
				System.out.println("Création de la config "
						+ f.getCanonicalPath());
				Config.saveConfig(f);
			}
		} else {
			String fileName = "profiler.xml";
			File f1 = new File(fileName);
			File f2 = new File(".", fileName);
			File f3 = new File("C:\\", fileName);
			if (f == null || !f.exists()) {
				f = f1;
				if (!f.exists()) {
					f = f2;
					if (!f2.exists()) {
						f = f3;
					}
				}
			}

			if (!f.exists()) {
				System.out.println("Pas de config : ");
				System.out.println(" - ${profiler.config}");
				System.out.println(" - " + f1.getCanonicalPath());
				System.out.println(" - " + f2.getCanonicalPath());
				System.out.println(" - " + f3.getCanonicalPath());
				System.out.println("Création de la config "
						+ f1.getCanonicalPath());
				Config.saveConfig(f1);
				f = f1;
			}
		}
		System.out.println("Config : " + f.getCanonicalPath());
		configBean = Config.loadConfig(f);
	}

	public static void agentmain(String agentArgument,
			Instrumentation instrumentation) {
		startAgent(agentArgument, instrumentation);

		try {
			ClassFileTransformer classFileTransformer = new ClassFileTransformer(
					agentArgument);
			Class<?>[] allLoadedClasses = instrumentation.getAllLoadedClasses();
			for (Class<?> class1 : allLoadedClasses) {
				try {
					String className = class1.getName();
					if (classFileTransformer.shouldTransform(className)) {
						byte[] newBytes = classFileTransformer
								.loadAndTransform(class1.getClassLoader(),
										className, class1.getClass(), class1
												.getProtectionDomain());
						instrumentation.redefineClasses(new ClassDefinition(
								class1, newBytes));
					}
				} catch (Exception e) {
					configBean.logError("Failed to instrument "
							+ class1.getName(), e);
				}
			}
		} catch (Exception e) {
			configBean.logError("Failed to init profiler", e);
		}

	}

	private static final void startAgent(String agentArgument,
			Instrumentation instrumentation) {
		configBean.logVerbose("Profiling start at " + new Date());
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				TraceRecorder.shutdown();
				configBean.logVerbose("Profiling stop at " + new Date());
			}
		});
		try {
			instrumentation.addTransformer(new ClassFileTransformer(
					agentArgument));
		} catch (Exception e) {
			configBean.logError("Failed to init profiler", e);
		}
	}

}
